package net.ib.mtalk.ui.attachment.camera;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import net.ib.mtalk.R;
import net.ib.mtalk.base.BaseActivity;
import net.ib.mtalk.constant.MTalkConstant;
import net.ib.mtalk.ui.widget.ShutterButton;
import net.ib.mtalk.ui.widget.ShutterButton.OnShutterButtonListener;
import net.ib.mtalk.util.ImageManager;
import net.ib.mtalk.util.MTalkLogWriter;
import net.ib.mtalk.util.NotifyingAsyncQueryHandler;
import net.ib.mtalk.util.NotifyingAsyncQueryHandler.AsyncQueryListener;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.database.Cursor;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.Size;
import android.location.Location;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.util.AttributeSet;
import android.view.Display;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.OrientationEventListener;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;

import com.android.camera.CameraHolder;



/**
 * The Camera activity which can preview and take pictures.
 * @author urunimi
 *
 */
public class CameraActivity extends BaseActivity implements
		SurfaceHolder.Callback, OnShutterButtonListener, AsyncQueryListener {

	protected NotifyingAsyncQueryHandler mQueryHandler;
	protected Context mContext;

	private static final int CROP_MSG = 1;
	private static final int FIRST_TIME_INIT = 2;
	private static final int RESTART_PREVIEW = 3;
	private static final int CLEAR_SCREEN_DELAY = 4;
	private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 5;

	// The subset of parameters we need to update in setCameraParameters().
	private static final int UPDATE_PARAM_INITIALIZE = 1;
//	private static final int UPDATE_PARAM_ZOOM = 2;
//	private static final int UPDATE_PARAM_PREFERENCE = 4;
	private static final int UPDATE_PARAM_ALL = -1;

	// When setCameraParametersWhenIdle() is called, we accumulate the subsets
	// needed to be updated in mUpdateSet.
	private int mUpdateSet;

	// The brightness settings used when it is set to automatic in the system.
	// The reason why it is set to 0.7 is just because 1.0 is too bright.
//	private static final float DEFAULT_CAMERA_BRIGHTNESS = 0.7f;

	private static final int SCREEN_DELAY = 2 * 60 * 1000;
	private static final int FOCUS_BEEP_VOLUME = 100;

	private Parameters mParameters;
//	private Parameters mInitialParams;

	private OrientationEventListener mOrientationListener;
	private int mLastOrientation = 0; // No rotation (landscape) by default.
//	private SharedPreferences mPreferences;

	private static final int IDLE = 1;
	private static final int SNAPSHOT_IN_PROGRESS = 2;
	
	private int mStatus = IDLE;
	private static final String sTempCropFilename = "crop-temp";

	private android.hardware.Camera mCameraDevice;
	private ContentProviderClient mMediaProviderClient;
	private SurfaceView mSurfaceView;
	private SurfaceHolder mSurfaceHolder = null;
	private FocusRectangle mFocusRectangle;
	private ToneGenerator mFocusToneGenerator;
	private boolean mStartPreviewFail = false;

	private ImageCapture mImageCapture = null;

	private boolean mPreviewing;
	private boolean mPausing;
	private boolean mFirstTimeInitialized;
	private boolean mIsImageCaptureIntent;

	private static final int FOCUS_NOT_STARTED = 0;
	private static final int FOCUSING = 1;
	private static final int FOCUSING_SNAP_ON_FINISH = 2;
	private static final int FOCUS_SUCCESS = 3;
	private static final int FOCUS_FAIL = 4;
	private int mFocusState = FOCUS_NOT_STARTED;

	private final ShutterCallback mShutterCallback = new ShutterCallback();
	private final PostViewPictureCallback mPostViewPictureCallback = new PostViewPictureCallback();
	private final RawPictureCallback mRawPictureCallback = new RawPictureCallback();
	private final AutoFocusCallback mAutoFocusCallback = new AutoFocusCallback();
	
	// Use the ErrorCallback to capture the crash count
	// on the mediaserver
	private final ErrorCallback mErrorCallback = new ErrorCallback();

	private long mFocusStartTime;
	private long mFocusCallbackTime;
	private long mCaptureStartTime;
	private long mShutterCallbackTime;
	private long mPostViewPictureCallbackTime;
	private long mRawPictureCallbackTime;
	private long mJpegPictureCallbackTime;
//	private int mPicturesRemaining;

	// These latency time are for the CameraLatency test.
	public long mAutoFocusTime;
	public long mShutterLag;
	public long mShutterToPictureDisplayedTime;
	public long mPictureDisplayedToJpegCallbackTime;
	public long mJpegCallbackFinishTime;

	// Add for test
	public static boolean mMediaServerDied = false;

	// Focus mode. Options are pref_camera_focusmode_entryvalues.
	private String mFocusMode;
	private ShutterButton mShutterButton;

	private final Handler mHandler = new MainHandler();
	private boolean mQuickCapture;

	
	@Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		mContext = this;
		setContentView(R.layout.camera_activity);
		mSurfaceView = (SurfaceView) findViewById(R.id.camera_preview);

		mQueryHandler = new NotifyingAsyncQueryHandler(getContentResolver(), this);
		// comment out -- unused now.
		// mQuickCapture = getQuickCaptureSettings();

		/*
		 * To reduce startup time, we start the preview in another thread. We
		 * make sure the preview is started at the end of onCreate.
		 */
		Thread startPreviewThread = new Thread(new Runnable() {
			public void run() {
				try {
					mStartPreviewFail = false;
					startPreview();
				} catch (Exception e) {
					MTalkLogWriter.e(e);
					// In eng build, we throw the exception so that test tool
					// can detect it and report it
					if ("eng".equals(Build.TYPE)) {
						throw new RuntimeException(e);
					}
					mStartPreviewFail = true;
				}
			}
		});
		startPreviewThread.start();

		// don't set mSurfaceHolder here. We have it set ONLY within
		// surfaceChanged / surfaceDestroyed, other parts of the code
		// assume that when it is set, the surface is also set.
		SurfaceHolder holder = mSurfaceView.getHolder();
		holder.addCallback(this);
		holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

		// Make sure preview is started.
		try {
			startPreviewThread.join();
			if (mStartPreviewFail) {
				finish();
				return;
			}
		} catch (InterruptedException ex) {
			// ignore
		}
	}

	@Override
	public void onStart() {
		super.onStart();
	}

	@Override
	public void onStop() {
		super.onStop();
		if (mMediaProviderClient != null) {
			mMediaProviderClient.release();
			mMediaProviderClient = null;
		}
	}
	
	/**
	 * This Handler is used to post message back onto the main thread of the
	 * application
	 */
	private class MainHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case RESTART_PREVIEW: {
				restartPreview();
				if (mJpegPictureCallbackTime != 0) {
					long now = System.currentTimeMillis();
					mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
					MTalkLogWriter.v("mJpegCallbackFinishTime = "
							+ mJpegCallbackFinishTime + "ms");
					mJpegPictureCallbackTime = 0;
				}
				break;
			}

			case CLEAR_SCREEN_DELAY: {
				getWindow().clearFlags(
						WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
				break;
			}

			case FIRST_TIME_INIT: {
				initializeFirstTime();
				break;
			}

			case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
				setCameraParametersWhenIdle(0);
				break;
			}
			}
		}
	}

	private void keepMediaProviderInstance() {
		// We want to keep a reference to MediaProvider in camera's lifecycle.
		// ContentResolver calls.
		if (mMediaProviderClient == null) {
			mMediaProviderClient = getContentResolver()
					.acquireContentProviderClient(MediaStore.AUTHORITY);
		}
	}

	// Snapshots can only be taken after this is called. It should be called
	// once only. We could have done these things in onCreate() but we want to
	// make preview screen appear as soon as possible.
	private void initializeFirstTime() {
		if (mFirstTimeInitialized)
			return;

		keepMediaProviderInstance();
		
		// Initialize shutter button.
		mShutterButton = (ShutterButton) findViewById(R.id.shutterButton);
		mShutterButton.setOnShutterButtonListener(this);
		mShutterButton.setVisibility(View.VISIBLE);

		mFocusRectangle = (FocusRectangle) findViewById(R.id.focus_rectangle);
		updateFocusIndicator();

//		initializeScreenBrightness();
		installIntentFilter();
		initializeFocusTone();
		initializeZoom();

		mFirstTimeInitialized = true;
	}

	// If the activity is paused and resumed, this method will be called in
	// onResume.
	private void initializeSecondTime() {
		// Start orientation listener as soon as possible because it takes
		// some time to get first orientation.
		installIntentFilter();

		initializeFocusTone();

		keepMediaProviderInstance();
	}

	private void initializeZoom() {
//		if (!mParameters.isZoomSupported())
//			return;

		// Maximum zoom value may change after preview size is set. Get the
		// latest parameters here.
		mParameters = mCameraDevice.getParameters();
	}


	private final class ShutterCallback implements
			android.hardware.Camera.ShutterCallback {
		public void onShutter() {
			mShutterCallbackTime = System.currentTimeMillis();
			mShutterLag = mShutterCallbackTime - mCaptureStartTime;
			MTalkLogWriter.v( "mShutterLag = " + mShutterLag + "ms");
			clearFocusState();
		}
	}

	private final class PostViewPictureCallback implements PictureCallback {
		public void onPictureTaken(byte[] data, android.hardware.Camera camera) {
			mPostViewPictureCallbackTime = System.currentTimeMillis();
			MTalkLogWriter.v( "mShutterToPostViewCallbackTime = "
					+ (mPostViewPictureCallbackTime - mShutterCallbackTime)
					+ "ms");
		}
	}

	private final class RawPictureCallback implements PictureCallback {
		public void onPictureTaken(byte[] rawData,
				android.hardware.Camera camera) {
			mRawPictureCallbackTime = System.currentTimeMillis();
			MTalkLogWriter.v( "mShutterToRawCallbackTime = "
					+ (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
		}
	}

	private final class JpegPictureCallback implements PictureCallback {
		Location mLocation;

		public JpegPictureCallback(Location loc) {
			mLocation = loc;
		}

		public void onPictureTaken(final byte[] jpegData,
				final android.hardware.Camera camera) {
			if (mPausing) {
				return;
			}

			mJpegPictureCallbackTime = System.currentTimeMillis();
			// If postview callback has arrived, the captured image is displayed
			// in postview callback. If not, the captured image is displayed in
			// raw picture callback.
			if (mPostViewPictureCallbackTime != 0) {
				mShutterToPictureDisplayedTime = mPostViewPictureCallbackTime
						- mShutterCallbackTime;
				mPictureDisplayedToJpegCallbackTime = mJpegPictureCallbackTime
						- mPostViewPictureCallbackTime;
			} else {
				mShutterToPictureDisplayedTime = mRawPictureCallbackTime
						- mShutterCallbackTime;
				mPictureDisplayedToJpegCallbackTime = mJpegPictureCallbackTime
						- mRawPictureCallbackTime;
			}
			MTalkLogWriter.v("mPictureDisplayedToJpegCallbackTime = "
					+ mPictureDisplayedToJpegCallbackTime + "ms");

			if (!mIsImageCaptureIntent) {
				// We want to show the taken picture for a while, so we wait
				// for at least 1.2 second before restarting the preview.
				long delay = 1200 - mPictureDisplayedToJpegCallbackTime;
				if (delay < 0 || mQuickCapture) {
					restartPreview();
				} else {
					mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, delay);
				}
			}
			mImageCapture.storeImage(jpegData, mLocation);

			// Calculate this in advance of each shot so we don't add to shutter
			// latency. It's true that someone else could write to the SD card
			// in
			// the mean time and fill it, but that could have happened between
			// the
			// shutter press and saving the JPEG too.

			if (!mHandler.hasMessages(RESTART_PREVIEW)) {
				long now = System.currentTimeMillis();
				mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
				MTalkLogWriter.v("mJpegCallbackFinishTime = "
						+ mJpegCallbackFinishTime + "ms");
				mJpegPictureCallbackTime = 0;
			}
		}
	}

	private final class AutoFocusCallback implements
			android.hardware.Camera.AutoFocusCallback {
		public void onAutoFocus(boolean focused, android.hardware.Camera camera) {
			mFocusCallbackTime = System.currentTimeMillis();
			mAutoFocusTime = mFocusCallbackTime - mFocusStartTime;
			MTalkLogWriter.v("mAutoFocusTime = " + mAutoFocusTime + "ms");
			if (mFocusState == FOCUSING_SNAP_ON_FINISH) {
				// Take the picture no matter focus succeeds or fails. No need
				// to play the AF sound if we're about to play the shutter
				// sound.
				if (focused) {
					mFocusState = FOCUS_SUCCESS;
				} else {
					mFocusState = FOCUS_FAIL;
				}
				mImageCapture.onSnap();
			} else if (mFocusState == FOCUSING) {
				// User is half-pressing the focus key. Play the focus tone.
				// Do not take the picture now.
				ToneGenerator tg = mFocusToneGenerator;
				if (tg != null) {
					tg.startTone(ToneGenerator.TONE_PROP_BEEP2);
				}
				if (focused) {
					mFocusState = FOCUS_SUCCESS;
				} else {
					mFocusState = FOCUS_FAIL;
				}
			} else if (mFocusState == FOCUS_NOT_STARTED) {
				// User has released the focus key before focus completes.
				// Do nothing.
			}
			updateFocusIndicator();
		}
	}

	private static final class ErrorCallback implements
			android.hardware.Camera.ErrorCallback {
		public void onError(int error, android.hardware.Camera camera) {
			if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) {
				mMediaServerDied = true;
				MTalkLogWriter.v("media server died");
			}
		}
	}

	private class ImageCapture {
		
		// Returns the rotation degree in the jpeg header.
		private int storeImage(byte[] data, Location loc) {
			try {
				String directory = MTalkConstant.MTALK_PATH + "/camera";
				File root = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), directory);
				if(!root.canWrite())root.mkdirs();
				
				long dateTaken = System.currentTimeMillis();
				String title = createName(dateTaken);
				String filename = title + ".jpg";
				int[] degree = new int[1];
				Uri contentUri = ImageManager.addImage(getContentResolver(), title,
						dateTaken,
						null, // location from gps/network
						root.getAbsolutePath(), filename, null,
						data, degree);
				Intent intent = new Intent();
				intent.setData(contentUri);
				setResult(RESULT_OK, intent);
				finish();
//				ContentValues cv = new ContentValues();
//				cv.put(ImageTable.TIME, dateTaken);
//				cv.put(ImageTable.URI, contentUri.toString());
//				if(mVideo != null) {
//					if(mVideo.Address != null) cv.put(ImageTable.ADDRESS, mVideo.Address);
//					cv.put(ImageTable.VIDEO_ID, mVideo.Id);
//					++mVideo.ImageCount;
//					VideoModel.update(mQueryHandler, mVideo);
//				}
//				
//				mQueryHandler.startInsert(ImageTable.CONTENT_URI, cv);
				return degree[0];
			} catch (Exception ex) {
				MTalkLogWriter.e(ex);
				return 0;
			}
		}

		/**
		 * Initiate the capture of an image.
		 */
		public void initiate() {
			if (mCameraDevice == null) {
				return;
			}

			capture();
		}

		private void capture() {

			// Set rotation.
			int orientation = mLastOrientation;
			if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
				orientation += 90;
			}

			orientation = roundOrientation(orientation);
			
			mParameters.setRotation(orientation);

			// Clear previous GPS location from the parameters.
			mParameters.removeGpsData();

			// We always encode GpsTimeStamp
			mParameters.setGpsTimestamp(System.currentTimeMillis() / 1000);


			mCameraDevice.setParameters(mParameters);

			mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
					mPostViewPictureCallback, new JpegPictureCallback(null));
			mPreviewing = false;
		}

		public void onSnap() {
			// If we are already in the middle of taking a snapshot then ignore.
			if (mPausing || mStatus == SNAPSHOT_IN_PROGRESS) {
				return;
			}
			mCaptureStartTime = System.currentTimeMillis();
			mPostViewPictureCallbackTime = 0;
			mStatus = SNAPSHOT_IN_PROGRESS;

			mImageCapture.initiate();
		}
		
		public int roundOrientation(int orientationInput) {
			int orientation = orientationInput;
			if (orientation == -1) {
				orientation = 0;
			}

			orientation = orientation % 360;
			int retVal;
			if (orientation < (0 * 90) + 45) {
				retVal = 0;
			} else if (orientation < (1 * 90) + 45) {
				retVal = 90;
			} else if (orientation < (2 * 90) + 45) {
				retVal = 180;
			} else if (orientation < (3 * 90) + 45) {
				retVal = 270;
			} else {
				retVal = 0;
			}
			return retVal;
		}
	}

	private String createName(long dateTaken) {
		Date date = new Date(dateTaken);
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSSZ");

		return "mnTalkCamera_" + dateFormat.format(date);
	}

	

	public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
		if (mPausing) {
			return;
		}
		switch (button.getId()) {
		case R.id.shutterButton:
			doFocus(pressed);
			break;
		}
	}

	public void onShutterButtonClick(ShutterButton button) {
		if (mPausing) {
			return;
		}
		switch (button.getId()) {
		case R.id.shutterButton:
			doSnap();
			break;
		}
	}

	private void installIntentFilter() {
		// install an intent filter to receive SD card related events.
		IntentFilter intentFilter = new IntentFilter(
				Intent.ACTION_MEDIA_MOUNTED);
		intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
		intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
		intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
		intentFilter.addDataScheme("file");
	}

	private void initializeFocusTone() {
		// Initialize focus tone generator.
		try {
			mFocusToneGenerator = new ToneGenerator(AudioManager.STREAM_SYSTEM,
					FOCUS_BEEP_VOLUME);
		} catch (Throwable ex) {
			MTalkLogWriter.e(ex);
			mFocusToneGenerator = null;
		}
	}

	@Override
	public void onResume() {
		super.onResume();

		mPausing = false;
		mJpegPictureCallbackTime = 0;
		mImageCapture = new ImageCapture();
		
		// Create orientation listenter. This should be done first because it
		// takes some time to get first orientation.
		if(mOrientationListener == null) {
			mOrientationListener = new OrientationEventListener(CameraActivity.this) {
				@Override
				public void onOrientationChanged(int orientation) {
					// We keep the last known orientation. So if the user
					// first orient the camera then point the camera to
					mLastOrientation = orientation;
				}
			};
		}
			
		mOrientationListener.enable();
		// Start the preview if it is not started.
		if (!mPreviewing && !mStartPreviewFail) {
			try {
				startPreview();
			} catch (Exception e) {
				finish();
				return;
			}
		}

		if (mSurfaceHolder != null) {
			// If first time initialization is not finished, put it in the
			// message queue.
			if (!mFirstTimeInitialized) {
				mHandler.sendEmptyMessage(FIRST_TIME_INIT);
			} else {
				initializeSecondTime();
			}
		}
		keepScreenOnAwhile();
	}

	@Override
	public void onConfigurationChanged(Configuration config) {
		super.onConfigurationChanged(config);
	}

	@Override
	protected void onPause() {
		mPausing = true;
		stopPreview();
		// Close the camera now because other activities may need to use it.
		closeCamera();
		resetScreenOn();

		mOrientationListener.disable();

		if (mFocusToneGenerator != null) {
			mFocusToneGenerator.release();
			mFocusToneGenerator = null;
		}

		// Remove the messages in the event queue.
		mHandler.removeMessages(RESTART_PREVIEW);
		mHandler.removeMessages(FIRST_TIME_INIT);

		super.onPause();
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		switch (requestCode) {
		case CROP_MSG: {
			Intent intent = new Intent();
			if (data != null) {
				Bundle extras = data.getExtras();
				if (extras != null) {
					intent.putExtras(extras);
				}
			}
			setResult(resultCode, intent);
			finish();

			File path = getFileStreamPath(sTempCropFilename);
			path.delete();

			break;
		}
		}
	}

	private boolean canTakePicture() {
		return isCameraIdle() && mPreviewing;
	}

	private void autoFocus() {
		// Initiate autofocus only when preview is started and snapshot is not
		// in progress.
		if (canTakePicture()) {
			MTalkLogWriter.v("Start autofocus.");
			mFocusStartTime = System.currentTimeMillis();
			mFocusState = FOCUSING;
			updateFocusIndicator();
			mCameraDevice.autoFocus(mAutoFocusCallback);
		}
	}

	private void cancelAutoFocus() {
		// User releases half-pressed focus key.
		if (mFocusState == FOCUSING || mFocusState == FOCUS_SUCCESS
				|| mFocusState == FOCUS_FAIL) {
			MTalkLogWriter.v("Cancel autofocus.");
			mCameraDevice.cancelAutoFocus();
		}
		if (mFocusState != FOCUSING_SNAP_ON_FINISH) {
			clearFocusState();
		}
	}

	private void clearFocusState() {
		mFocusState = FOCUS_NOT_STARTED;
		updateFocusIndicator();
	}

	private void updateFocusIndicator() {
		if (mFocusRectangle == null)
			return;

		if (mFocusState == FOCUSING || mFocusState == FOCUSING_SNAP_ON_FINISH) {
			mFocusRectangle.showStart();
		} else if (mFocusState == FOCUS_SUCCESS) {
			mFocusRectangle.showSuccess();
		} else if (mFocusState == FOCUS_FAIL) {
			mFocusRectangle.showFail();
		} else {
			mFocusRectangle.clear();
		}
	}

	@Override
	public void onBackPressed() {
		if (!isCameraIdle()) {
			return;
		} else {
			super.onBackPressed();
		}
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		switch (keyCode) {
		case KeyEvent.KEYCODE_FOCUS:
			if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
				doFocus(true);
			}
			return true;
		case KeyEvent.KEYCODE_CAMERA:
			if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
				doSnap();
			}
			return true;
		case KeyEvent.KEYCODE_DPAD_CENTER:
			// If we get a dpad center event without any focused view, move
			// the focus to the shutter button and press it.
			if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
				// Start auto-focus immediately to reduce shutter lag. After
				// the shutter button gets the focus, doFocus() will be
				// called again but it is fine.
				doFocus(true);
				if (mShutterButton.isInTouchMode()) {
					mShutterButton.requestFocusFromTouch();
				} else {
					mShutterButton.requestFocus();
				}
				mShutterButton.setPressed(true);
			}
			return true;
		}

		return super.onKeyDown(keyCode, event);
	}

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		switch (keyCode) {
		case KeyEvent.KEYCODE_FOCUS:
			if (mFirstTimeInitialized) {
				doFocus(false);
			}
			return true;
		}
		return super.onKeyUp(keyCode, event);
	}

	private void doSnap() {

		MTalkLogWriter.v("doSnap: mFocusState=" + mFocusState);
		// If the user has half-pressed the shutter and focus is completed, we
		// can take the photo right away. If the focus mode is infinity, we can
		// also take the photo.
		if (mFocusMode.equals(Parameters.FOCUS_MODE_INFINITY)
				|| (mFocusState == FOCUS_SUCCESS || mFocusState == FOCUS_FAIL)) {
			mImageCapture.onSnap();
		} else if (mFocusState == FOCUSING) {
			// Half pressing the shutter (i.e. the focus button event) will
			// already have requested AF for us, so just request capture on
			// focus here.
			mFocusState = FOCUSING_SNAP_ON_FINISH;
		} else if (mFocusState == FOCUS_NOT_STARTED) {
			// Focus key down event is dropped for some reasons. Just ignore.
		}
	}

	private void doFocus(boolean pressed) {
		// Do the focus if the mode is not infinity.
		
		if (!mFocusMode.equals(Parameters.FOCUS_MODE_INFINITY)) {
			if (pressed) { // Focus key down.
				autoFocus();
			} else { // Focus key up.
				cancelAutoFocus();
			}
		}
	}

	public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
		// Make sure we have a surface in the holder before proceeding.
		if (holder.getSurface() == null) {
			MTalkLogWriter.d("holder.getSurface() == null");
			return;
		}

		// We need to save the holder for later use, even when the mCameraDevice
		// is null. This could happen if onResume() is invoked after this
		// function.
		mSurfaceHolder = holder;

		// The mCameraDevice will be null if it fails to connect to the camera
		// hardware. In this case we will show a dialog and then finish the
		// activity, so it's OK to ignore it.
		if (mCameraDevice == null)
			return;

		// Sometimes surfaceChanged is called after onPause or before onResume.
		// Ignore it.
		if (mPausing || isFinishing())
			return;

		if (mPreviewing && holder.isCreating()) {
			// Set preview display if the surface is being created and preview
			// was already started. That means preview display was set to null
			// and we need to set it now.
			setPreviewDisplay(holder);
		} else {
			// 1. Restart the preview if the size of surface was changed. The
			// framework may not support changing preview display on the fly.
			// 2. Start the preview now if surface was destroyed and preview
			// stopped.
			restartPreview();
		}

		// If first time initialization is not finished, send a message to do
		// it later. We want to finish surfaceChanged as soon as possible to let
		// user see preview first.
		if (!mFirstTimeInitialized) {
			mHandler.sendEmptyMessage(FIRST_TIME_INIT);
		} else {
			initializeSecondTime();
		}
	}

	public void surfaceCreated(SurfaceHolder holder) {
	}

	public void surfaceDestroyed(SurfaceHolder holder) {
		stopPreview();
		mSurfaceHolder = null;
	}

	private void closeCamera() {
		if (mCameraDevice != null) {
			CameraHolder.instance().release();
//			mCameraDevice.setZoomChangeListener(null);
			mCameraDevice = null;
			mPreviewing = false;
		}
	}

	private void ensureCameraDevice() throws Exception {
		if (mCameraDevice == null) {
			mCameraDevice = CameraHolder.instance().open();
//			mInitialParams = mCameraDevice.getParameters();
		}
	}

	private void restartPreview() {
		try {
			startPreview();
		} catch (Exception e) {
			finish();
			return;
		}
	}

	private void setPreviewDisplay(SurfaceHolder holder) {
		try {
			mCameraDevice.setPreviewDisplay(holder);
		} catch (Throwable ex) {
			closeCamera();
			throw new RuntimeException("setPreviewDisplay failed", ex);
		}
	}

	private void startPreview() throws Exception {
		if (mPausing || isFinishing())
			return;
		
		ensureCameraDevice();

		// If we're previewing already, stop the preview first (this will blank
		// the screen).
		if (mPreviewing)
			stopPreview();

		setPreviewDisplay(mSurfaceHolder);
		setCameraParameters(UPDATE_PARAM_ALL);

		mCameraDevice.setErrorCallback(mErrorCallback);

		try {
			MTalkLogWriter.v("startPreview");
			mCameraDevice.startPreview();
		} catch (Throwable ex) {
			closeCamera();
			throw new RuntimeException("startPreview failed", ex);
		}
		mPreviewing = true;
		mStatus = IDLE;
	}

	private void stopPreview() {
		if (mCameraDevice != null && mPreviewing) {
			MTalkLogWriter.v("stopPreview");
			mCameraDevice.stopPreview();
		}
		mPreviewing = false;
		// If auto focus was in progress, it would have been canceled.
		clearFocusState();
	}

	private Size getOptimalPreviewSize(List<Size> sizes, double targetRatio) {
		final double ASPECT_TOLERANCE = 0.05;
		if (sizes == null)
			return null;

		Size optimalSize = null;
		double minDiff = Double.MAX_VALUE;

		// Because of bugs of overlay and layout, we sometimes will try to
		// layout the viewfinder in the portrait orientation and thus get the
		// wrong size of mSurfaceView. When we change the preview size, the
		// new overlay will be created before the old one closed, which causes
		// an exception. For now, just get the screen size

		Display display = getWindowManager().getDefaultDisplay();
		int targetHeight = Math.min(display.getHeight(), display.getWidth());

		if (targetHeight <= 0) {
			// We don't know the size of SurefaceView, use screen height
			WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
			targetHeight = windowManager.getDefaultDisplay().getHeight();
		}

		// Try to find an size match aspect ratio and size
		for (Size size : sizes) {
			double ratio = (double) size.width / size.height;
			if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
				continue;
			if (Math.abs(size.height - targetHeight) < minDiff) {
				optimalSize = size;
				minDiff = Math.abs(size.height - targetHeight);
			}
		}

		// Cannot find the one match the aspect ratio, ignore the requirement
		if (optimalSize == null) {
			MTalkLogWriter.v("No preview size match the aspect ratio");
			minDiff = Double.MAX_VALUE;
			for (Size size : sizes) {
				if (Math.abs(size.height - targetHeight) < minDiff) {
					optimalSize = size;
					minDiff = Math.abs(size.height - targetHeight);
				}
			}
		}
		return optimalSize;
	}

	private static boolean isSupported(String value, List<String> supported) {
		return supported == null ? false : supported.indexOf(value) >= 0;
	}

	private void updateCameraParametersInitialize() {
		// Reset preview frame rate to the maximum because it may be lowered by
		// video camera application.
		List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
		if (frameRates != null) {
			Integer max = Collections.max(frameRates);
			mParameters.setPreviewFrameRate(max);
		}

	}

	// We separate the parameters into several subsets, so we can update only
	// the subsets actually need updating. The PREFERENCE set needs extra
	// locking because the preference can be changed from GLThread as well.
	private void setCameraParameters(int updateSet) {
		mParameters = mCameraDevice.getParameters();

		if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
			updateCameraParametersInitialize();
		}
		
//		synchronized (mPreferences) {
			updateCameraParametersPreference();
//		}

		if(mParameters.getFlashMode() != null) {
			mParameters.setFlashMode(Parameters.FLASH_MODE_AUTO);
		}

		mCameraDevice.setParameters(mParameters);
	}

	private void updateCameraParametersPreference() {

		// Set the preview frame aspect ratio according to the picture size.
		Size size = mParameters.getPictureSize();
		
		// Set a preview size that is closest to the viewfinder height and has
		// the right aspect ratio.
		List<Size> sizes = mParameters.getSupportedPreviewSizes();
		Size optimalSize = getOptimalPreviewSize(sizes, (double) size.width
				/ size.height);
		if (optimalSize != null) {
			mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
		}

		mFocusMode = Parameters.FOCUS_MODE_AUTO;
		if (isSupported(mFocusMode, mParameters.getSupportedFocusModes())) {
			mParameters.setFocusMode(mFocusMode);
		} else {
			mFocusMode = mParameters.getFocusMode();
			if (mFocusMode == null) {
				mFocusMode = Parameters.FOCUS_MODE_AUTO;
			}
		}
	}
	
	// If the Camera is idle, update the parameters immediately, otherwise
	// accumulate them in mUpdateSet and update later.
	private void setCameraParametersWhenIdle(int additionalUpdateSet) {
		mUpdateSet |= additionalUpdateSet;
		if (mCameraDevice == null) {
			// We will update all the parameters when we open the device, so
			// we don't need to do anything now.
			mUpdateSet = 0;
			return;
		} else if (isCameraIdle()) {
			setCameraParameters(mUpdateSet);
			mUpdateSet = 0;
		} else {
			if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
				mHandler.sendEmptyMessageDelayed(
						SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
			}
		}
	}

	private boolean isCameraIdle() {
		return mStatus == IDLE && mFocusState == FOCUS_NOT_STARTED;
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		super.onPrepareOptionsMenu(menu);
		// Only show the menu when camera is idle.
		for (int i = 0; i < menu.size(); i++) {
			menu.getItem(i).setVisible(isCameraIdle());
		}

		return true;
	}

	@Override
	public void onUserInteraction() {
		super.onUserInteraction();
		keepScreenOnAwhile();
	}

	private void resetScreenOn() {
		mHandler.removeMessages(CLEAR_SCREEN_DELAY);
		getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
	}

	private void keepScreenOnAwhile() {
		mHandler.removeMessages(CLEAR_SCREEN_DELAY);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
	}

	@Override
	public void onQueryComplete(int token, Object cookie, Cursor cursor) {}

	@Override
	public void onDeleteComplete(int token, Object cookie, int result) {}

	@Override
	public void onInsertComplete(int token, Object cookie, Uri uri) {}

	@Override
	public void onUpdateComplete(int token, Object cookie, int result) {}
}

class FocusRectangle extends View {

	public FocusRectangle(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	private void setDrawable(int resid) {
		setBackgroundDrawable(getResources().getDrawable(resid));
	}

	public void showStart() {
		setDrawable(R.drawable.focus_focusing);
	}

	public void showSuccess() {
		setDrawable(R.drawable.focus_focused);
	}

	public void showFail() {
		setDrawable(R.drawable.focus_focus_failed);
	}

	public void clear() {
		setBackgroundDrawable(null);
	}
}
